
Anh Tuan
Data Science Expert
TL;DR: Các crawler của Crawlee thường gặp rào cản CAPTCHA. Việc tích hợp CapSolver cho phép bạn giải các CAPTCHA như reCAPTCHA, Turnstile và nhiều loại khác, giúp quy trình quét dữ liệu ổn định và tự động hóa.

Khi xây dựng các crawler với Crawlee, việc gặp CAPTCHA là gần như không thể tránh khỏi—đặc biệt là trên các trang web hiện đại có bảo vệ bot mạnh mẽ. Ngay cả các crawler Playwright hoặc HTTP được cấu hình tốt cũng có thể bị chặn khi xuất hiện các bài kiểm tra reCAPTCHA, Turnstile hoặc tương tự.
Hướng dẫn này tập trung vào cách tiếp cận thực tế: sử dụng CapSolver để xử lý các bài kiểm tra CAPTCHA trực tiếp trong quy trình Crawlee. Thay vì phải chiến đấu với các dấu vân tay trình duyệt liên tục, bạn sẽ thấy cách phát hiện các loại CAPTCHA phổ biến, giải chúng một cách tự động hóa, và duy trì hoạt động ổn định của crawler trong các tình huống quét dữ liệu thực tế.
Crawlee là thư viện quét web và tự động hóa trình duyệt cho Node.js, được thiết kế để xây dựng các crawler đáng tin cậy, tạo cảm giác như người dùng thật và tránh bị phát hiện bởi các hệ thống bảo vệ bot hiện đại. Được xây dựng bằng TypeScript, nó cung cấp cả giao diện cấp cao đơn giản và tùy chỉnh cấp thấp.
Crawlee cung cấp nhiều loại crawler cho các trường hợp sử dụng khác nhau:
| Loại Crawler | Mô tả |
|---|---|
| CheerioCrawler | Crawler HTTP siêu nhanh sử dụng Cheerio để phân tích HTML |
| PlaywrightCrawler | Tự động hóa trình duyệt đầy đủ với Playwright cho các trang web nặng JavaScript |
| PuppeteerCrawler | Tự động hóa trình duyệt đầy đủ với Puppeteer cho việc render JavaScript |
| JSDOMCrawler | Crawler HTTP với JSDOM để thực thi JavaScript mà không cần trình duyệt |
CapSolver là dịch vụ giải CAPTCHA hàng đầu cung cấp các giải pháp dựa trên trí tuệ nhân tạo để vượt qua các bài kiểm tra CAPTCHA khác nhau. Với khả năng hỗ trợ nhiều loại CAPTCHA và thời gian phản hồi nhanh như chớp, CapSolver tích hợp liền mạch vào các quy trình tự động hóa.
Khi xây dựng các crawler Crawlee tương tác với các trang web được bảo vệ, các bài kiểm tra CAPTCHA có thể làm dừng toàn bộ quy trình quét dữ liệu của bạn. Dưới đây là lý do tại sao tích hợp này quan trọng:
Đầu tiên, cài đặt các gói cần thiết:
npm install crawlee playwright axios
Hoặc với yarn:
yarn add crawlee playwright axios
Dưới đây là lớp tiện ích CapSolver có thể tái sử dụng có thể được sử dụng trong các dự án Crawlee của bạn:
import axios from 'axios';
const CAPSOLVER_API_KEY = 'YOUR_CAPSOLVER_API_KEY';
interface TaskResult {
status: string;
solution?: {
gRecaptchaResponse?: string;
token?: string;
};
errorDescription?: string;
}
class CapSolverService {
private apiKey: string;
private baseUrl = 'https://api.capsolver.com';
constructor(apiKey: string = CAPSOLVER_API_KEY) {
this.apiKey = apiKey;
}
async createTask(taskData: object): Promise<string> {
const response = await axios.post(`${this.baseUrl}/createTask`, {
clientKey: this.apiKey,
task: taskData
});
if (response.data.errorId !== 0) {
throw new Error(`Lỗi CapSolver: ${response.data.errorDescription}`);
}
return response.data.taskId;
}
async getTaskResult(taskId: string, maxAttempts = 60): Promise<TaskResult> {
for (let i = 0; i < maxAttempts; i++) {
await this.sleep(2000);
const response = await axios.post(`${this.baseUrl}/getTaskResult`, {
clientKey: this.apiKey,
taskId
});
if (response.data.status === 'ready') {
return response.data;
}
if (response.data.status === 'failed') {
throw new Error(`Bài toán thất bại: ${response.data.errorDescription}`);
}
}
throw new Error('Hết thời gian chờ đợi giải CAPTCHA');
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async solveReCaptchaV2(websiteUrl: string, websiteKey: string): Promise<string> {
const taskId = await this.createTask({
type: 'ReCaptchaV2TaskProxyLess',
websiteURL: websiteUrl,
websiteKey
});
const result = await this.getTaskResult(taskId);
return result.solution?.gRecaptchaResponse || '';
}
async solveReCaptchaV3(
websiteUrl: string,
websiteKey: string,
pageAction = 'submit'
): Promise<string> {
const taskId = await this.createTask({
type: 'ReCaptchaV3TaskProxyLess',
websiteURL: websiteUrl,
websiteKey,
pageAction
});
const result = await this.getTaskResult(taskId);
return result.solution?.gRecaptchaResponse || '';
}
async solveTurnstile(websiteUrl: string, websiteKey: string): Promise<string> {
const taskId = await this.createTask({
type: 'AntiTurnstileTaskProxyLess',
websiteURL: websiteUrl,
websiteKey
});
const result = await this.getTaskResult(taskId);
return result.solution?.token || '';
}
}
export const capSolver = new CapSolverService();
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const RECAPTCHA_SITE_KEY = 'YOUR_SITE_KEY';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Đang xử lý ${request.url}`);
// Kiểm tra xem trang có reCAPTCHA không
const hasRecaptcha = await page.$('.g-recaptcha');
if (hasRecaptcha) {
log.info('Phát hiện reCAPTCHA, đang giải...');
// Lấy khóa trang từ trang
const siteKey = await page.$eval(
'.g-recaptcha',
(el) => el.getAttribute('data-sitekey')
) || RECAPTCHA_SITE_KEY;
// Giải CAPTCHA
const token = await capSolver.solveReCaptchaV2(request.url, siteKey);
// Chèn token - textarea bị ẩn, sử dụng JavaScript
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, token: string) => {
el.style.display = 'block';
el.value = token;
}, token);
// Gửi biểu mẫu
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
log.info('reCAPTCHA được giải thành công!');
}
// Trích xuất dữ liệu sau khi CAPTCHA được giải
const title = await page.title();
const content = await page.locator('body').innerText();
await Dataset.pushData({
title,
content: content.slice(0, 1000)
});
},
maxRequestsPerCrawl: 50,
headless: true
});
await crawler.run(['https://example.com/protected-page']);
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Đang xử lý ${request.url}`);
// reCAPTCHA v3 là ẩn, phát hiện bằng script
const recaptchaScript = await page.$('script[src*="recaptcha/api.js?render="]');
if (recaptchaScript) {
log.info('Phát hiện reCAPTCHA v3, đang giải...');
// Trích xuất khóa trang từ src script
const scriptSrc = await recaptchaScript.getAttribute('src') || '';
const siteKeyMatch = scriptSrc.match(/render=([^&]+)/);
const siteKey = siteKeyMatch ? siteKeyMatch[1] : '';
if (siteKey) {
// Giải reCAPTCHA v3
const token = await capSolver.solveReCaptchaV3(
request.url,
siteKey,
'submit'
);
// Chèn token vào trường ẩn bằng JavaScript
await page.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
log.info('Token reCAPTCHA v3 đã được chèn!');
}
}
// Tiếp tục gửi biểu mẫu hoặc trích xuất dữ liệu
const title = await page.title();
const url = page.url();
await Dataset.pushData({ title, url });
}
});
await crawler.run(['https://example.com/v3-protected']);
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Đang xử lý ${request.url}`);
// Kiểm tra xem có widget Turnstile không
const hasTurnstile = await page.$('.cf-turnstile');
if (hasTurnstile) {
log.info('Phát hiện Cloudflare Turnstile, đang giải...');
// Lấy khóa trang
const siteKey = await page.$eval(
'.cf-turnstile',
(el) => el.getAttribute('data-sitekey')
);
if (siteKey) {
// Giải Turnstile
const token = await capSolver.solveTurnstile(request.url, siteKey);
// Chèn token bằng JavaScript (trường ẩn)
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
// Gửi biểu mẫu
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
log.info('Turnstile được giải thành công!');
}
}
// Trích xuất dữ liệu
const title = await page.title();
const content = await page.locator('body').innerText();
await Dataset.pushData({
title,
content: content.slice(0, 500)
});
}
});
await crawler.run(['https://example.com/turnstile-protected']);
Dưới đây là một crawler nâng cao tự động phát hiện và giải các loại CAPTCHA khác nhau:
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
interface CaptchaInfo {
type: 'recaptcha-v2' | 'recaptcha-v3' | 'turnstile' | 'none';
siteKey: string | null;
}
async function detectCaptcha(page: any): Promise<CaptchaInfo> {
// Kiểm tra reCAPTCHA v2
const recaptchaV2 = await page.$('.g-recaptcha');
if (recaptchaV2) {
const siteKey = await page.$eval('.g-recaptcha', (el: Element) =>
el.getAttribute('data-sitekey')
);
return { type: 'recaptcha-v2', siteKey };
}
// Kiểm tra reCAPTCHA v3
const recaptchaV3Script = await page.$('script[src*="recaptcha/api.js?render="]');
if (recaptchaV3Script) {
const scriptSrc = await recaptchaV3Script.getAttribute('src') || '';
const match = scriptSrc.match(/render=([^&]+)/);
const siteKey = match ? match[1] : null;
return { type: 'recaptcha-v3', siteKey };
}
// Kiểm tra Turnstile
const turnstile = await page.$('.cf-turnstile');
if (turnstile) {
const siteKey = await page.$eval('.cf-turnstile', (el: Element) =>
el.getAttribute('data-sitekey')
);
return { type: 'turnstile', siteKey };
}
return { type: 'none', siteKey: null };
}
async function solveCaptcha(
page: any,
url: string,
captchaInfo: CaptchaInfo
): Promise<void> {
if (!captchaInfo.siteKey || captchaInfo.type === 'none') return;
let token: string;
switch (captchaInfo.type) {
case 'recaptcha-v2':
token = await capSolver.solveReCaptchaV2(url, captchaInfo.siteKey);
// Trường textarea ẩn - sử dụng JavaScript để đặt giá trị
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, t: string) => {
el.style.display = 'block';
el.value = t;
}, token);
break;
case 'recaptcha-v3':
token = await capSolver.solveReCaptchaV3(url, captchaInfo.siteKey);
// Trường ẩn - sử dụng JavaScript để đặt giá trị
await page.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
break;
case 'turnstile':
token = await capSolver.solveTurnstile(url, captchaInfo.siteKey);
// Trường ẩn - sử dụng JavaScript để đặt giá trị
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
break;
}
}
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log, enqueueLinks }) {
log.info(`Đang xử lý ${request.url}`);
// Tự động phát hiện CAPTCHA
const captchaInfo = await detectCaptcha(page);
if (captchaInfo.type !== 'none') {
log.info(`Phát hiện ${captchaInfo.type}, đang giải...`);
await solveCaptcha(page, request.url, captchaInfo);
// Gửi biểu mẫu nếu tồn tại
const submitBtn = await page.$('button[type="submit"], input[type="submit"]');
if (submitBtn) {
await submitBtn.click();
await page.waitForLoadState('networkidle');
}
log.info('CAPTCHA đã được giải thành công!');
}
// Trích xuất dữ liệu
const title = await page.title();
const url = page.url();
const text = await page.locator('body').innerText();
await Dataset.pushData({
title,
url,
text: text.slice(0, 1000)
});
// Tiếp tục quét
await enqueueLinks();
},
maxRequestsPerCrawl: 100
});
await crawler.run(['https://example.com']);
Mỗi loại CAPTCHA yêu cầu phương pháp gửi khác nhau trong ngữ cảnh trình duyệt:
async function submitRecaptchaToken(page: any, token: string): Promise<void> {
// Ô nhập phản hồi bị ẩn - sử dụng JavaScript để đặt giá trị
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, token: string) => {
el.style.display = 'block';
el.value = token;
}, token);
// Đặt giá trị cho trường ẩn nếu tồn tại (thường gặp trong các triển khai tùy chỉnh)
try {
await page.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
} catch (e) {
// Trường có thể không tồn tại
}
// Gửi biểu mẫu
await page.click('form button[type="submit"]');
}
async function submitTurnstileToken(page: any, token: string): Promise<void> {
// Đặt token vào trường ẩn bằng JavaScript
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
// Gửi biểu mẫu
await page.click('form button[type="submit"]');
}
Đối với các tình huống bạn muốn giải CAPTCHA tự động, bạn có thể tải mở rộng CapSolver:
import { PlaywrightCrawler } from 'crawlee';
import path from 'path';
const crawler = new PlaywrightCrawler({
launchContext: {
launchOptions: {
// Tải mở rộng CapSolver
args: [
`--disable-extensions-except=${path.resolve('./capsolver-extension')}`,
`--load-extension=${path.resolve('./capsolver-extension')}`
],
headless: false // Mở rộng yêu cầu chế độ có giao diện
}
},
async requestHandler({ page, request, log }) {
log.info(`Đang xử lý ${request.url}`);
// Mở rộng sẽ tự động giải CAPTCHA
// Chờ CAPTCHA được giải
await page.waitForTimeout(5000);
// Tiếp tục quét
const title = await page.title();
const content = await page.locator('body').innerText();
console.log({ title, content });
}
});
await crawler.run(['https://example.com/captcha-page']);
async function solveWithRetry(
solverFn: () => Promise<string>,
maxRetries = 3
): Promise<string> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await solverFn();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = Math.pow(2, attempt) * 1000; // Chờ tăng dần
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Đã vượt quá số lần thử');
}
// Sử dụng
const token = await solveWithRetry(() =>
capSolver.solveReCaptchaV2(url, siteKey)
);
import axios from 'axios';
async function checkBalance(apiKey: string): Promise<number> {
const response = await axios.post('https://api.capsolver.com/getBalance', {
clientKey: apiKey
});
return response.data.balance || 0;
}
// Kiểm tra trước khi bắt đầu crawler
const balance = await checkBalance(CAPSOLVER_API_KEY);
if (balance < 1) {
console.warn('Số dư CapSolver thấp! Vui lòng nạp thêm.');
}
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
// Lưu trữ token đã giải cho cùng một miền và khóa
const tokenCache = new Map<string, { token: string; timestamp: number }>();
const TOKEN_TTL = 90000; // 90 giây
async function getCachedToken(
url: string,
siteKey: string,
solverFn: () => Promise<string>
): Promise<string> {
const cacheKey = `${new URL(url).hostname}:${siteKey}`;
const cached = tokenCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < TOKEN_TTL) {
return cached.token;
}
const token = await solverFn();
tokenCache.set(cacheKey, { token, timestamp: Date.now() });
return token;
}
import { PlaywrightCrawler, ProxyConfiguration } from 'crawlee';
const proxyConfiguration = new ProxyConfiguration({
proxyUrls: [
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080'
]
});
const crawler = new PlaywrightCrawler({
proxyConfiguration,
async requestHandler({ page, request, log, proxyInfo }) {
log.info(`Đang sử dụng proxy: ${proxyInfo?.url}`);
// Logic giải CAPTCHA và quét dữ liệu ở đây
}
});
import { PlaywrightCrawler, Dataset, ProxyConfiguration } from 'crawlee';
import { capSolver } from './capsolver-service';
interface Product {
name: string;
price: string;
url: string;
image: string;
}
const proxyConfiguration = new ProxyConfiguration({
proxyUrls: ['http://user:pass@proxy.example.com:8080']
});
const crawler = new PlaywrightCrawler({
proxyConfiguration,
maxRequestsPerCrawl: 200,
maxConcurrency: 5,
async requestHandler({ page, request, log, enqueueLinks }) {
log.info(`Đang quét: ${request.url}`);
// Kiểm tra CAPTCHA
const hasRecaptcha = await page.$('.g-recaptcha');
const hasTurnstile = await page.$('.cf-turnstile');
if (hasRecaptcha) {
const siteKey = await page.$eval(
'.g-recaptcha',
(el) => el.getAttribute('data-sitekey')
);
if (siteKey) {
log.info('Đang giải reCAPTCHA...');
const token = await capSolver.solveReCaptchaV2(request.url, siteKey);
// Chèn token bằng JavaScript (phần tử ẩn)
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, t: string) => {
el.style.display = 'block';
el.value = t;
}, token);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
}
if (hasTurnstile) {
const siteKey = await page.$eval(
'.cf-turnstile',
(el) => el.getAttribute('data-sitekey')
);
if (siteKey) {
log.info('Đang giải Turnstile...');
const token = await capSolver.solveTurnstile(request.url, siteKey);
// Chèn token bằng JavaScript (phần tử ẩn)
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
}
// Trích xuất dữ liệu sản phẩm bằng Playwright locators
const productCards = await page.locator('.product-card').all();
const products: Product[] = [];
for (const card of productCards) {
products.push({
name: await card.locator('.product-name').innerText().catch(() => ''),
price: await card.locator('.product-price').innerText().catch(() => ''),
url: await card.locator('a').getAttribute('href') || '',
image: await card.locator('img').getAttribute('src') || ''
});
}
if (products.length > 0) {
await Dataset.pushData(products);
log.info(`Đã trích xuất ${products.length} sản phẩm`);
}
// Gửi các liên kết phân trang và danh mục
await enqueueLinks({
globs: ['**/products/**', '**/page/**', '**/category/**']
});
},
failedRequestHandler({ request, log }) {
log.error(`Yêu cầu thất bại: ${request.url}`);
}
});
// Bắt đầu quét
await crawler.run(['https://example-store.com/products']);
// Xuất kết quả
const dataset = await Dataset.open();
await dataset.exportToCSV('products.csv');
console.log('Quét hoàn tất! Kết quả được lưu vào products.csv');
Việc tích hợp CapSolver với Crawlee mở ra tiềm năng đầy đủ của việc quét web cho các nhà phát triển Node.js. Bằng cách kết hợp cơ sở hạ tầng quét mạnh mẽ của Crawlee với khả năng giải CAPTCHA hàng đầu của CapSolver, bạn có thể xây dựng các công cụ quét đáng tin cậy có thể xử lý các cơ chế bảo vệ bot khó khăn nhất.
Dù bạn đang xây dựng các pipeline trích xuất dữ liệu, hệ thống theo dõi giá cả hay các công cụ tổng hợp nội dung, sự kết hợp giữa Crawlee và CapSolver cung cấp độ tin cậy và khả năng mở rộng cần thiết cho các môi trường sản xuất.
Sẵn sàng bắt đầu chưa? Đăng ký CapSolver và sử dụng mã khuyến mãi CRAWLEE để nhận thêm 6% khuyến mãi cho mỗi lần nạp tiền!
Crawlee là thư viện quét web và tự động hóa trình duyệt cho Node.js được thiết kế để xây dựng các công cụ quét đáng tin cậy. Nó hỗ trợ cả quét dựa trên HTTP (với Cheerio/JSDOM) và tự động hóa trình duyệt đầy đủ (với Playwright/Puppeteer), bao gồm các tính năng tích hợp như xoay proxy, quản lý phiên và chống bot.
CapSolver tích hợp với Crawlee thông qua một lớp dịch vụ bao bọc API của CapSolver. Trong hàm xử lý yêu cầu của crawler, bạn có thể phát hiện các thách thức CAPTCHA và sử dụng CapSolver để giải chúng, sau đó chèn các token trở lại trang.
CapSolver hỗ trợ nhiều loại CAPTCHA bao gồm reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, AWS WAF, GeeTest và nhiều loại khác.
CapSolver cung cấp giá cả cạnh tranh dựa trên loại và khối lượng CAPTCHA được giải. Truy cập capsolver.com để xem thông tin giá cả hiện tại. Sử dụng mã CRAWLEE để nhận 6% khuyến mãi cho lần nạp đầu tiên.
Có! CapSolver cung cấp API REST có thể tích hợp với bất kỳ khung công tác Node.js nào, bao gồm Express, Puppeteer standalone, Selenium và nhiều hơn nữa.
Có, Crawlee là mã nguồn mở và được phát hành dưới giấy phép Apache 2.0. Khung công tác này miễn phí để sử dụng, dù bạn có thể phải chịu chi phí cho các dịch vụ proxy và dịch vụ giải CAPTCHA như CapSolver.
Khóa site thường được tìm thấy trong mã nguồn trang. Tìm kiếm:
data-sitekey trên phần tử .g-recaptchadata-sitekey trên phần tử .cf-turnstileHọc kiến trúc gỡ mã web Rust có thể mở rộng với reqwest, scraper, gỡ mã bất đồng bộ, gỡ mã trình duyệt không đầu, xoay proxy và xử lý CAPTCHA tuân thủ.

Tự động hóa việc giải CAPTCHA với Nanobot và CapSolver. Sử dụng Playwright để giải reCAPTCHA và Cloudflare tự động.
